import tkinter as tk
from .geo_base import Point, ManagerBase, Line
from itertools import filterfalse
import os
from time import strftime
from tkinter import filedialog
from functools import partial
import cv2
from typing import Callable, Any

Tags = tuple[int, str]
POINT_MODE = 0
LINE_MODE = 1
CROSS_MODE = 2


class UI:
    def __init__(self, *args, **kwargs) -> None:
        """
        Arguments:
          - `is_show`: 是否装运算结果可视化至前端
        """
        self.points_manager = ManagerBase()
        self._root = tk.Tk()
        self._root.title("获取鼠标点击位置")
        self._hline = None  # type : Tags
        self._bg_image = None
        self._bg_path = None
        self._ind = 0
        self.mode = 0
        self.point_cache = []
        self.hline_cache = None
        self.cross_line_cache = []
        self._canvas: tk.Canvas = None

    def _botton(self, rectifier: Callable[[list], cv2.Mat], is_show: bool, **kwargs):
        def enable_lines():
            self.mode = LINE_MODE

        def enable_point():
            self.mode = POINT_MODE

        def enable_finish(
            rectifier: Callable[[list], cv2.Mat], is_show: bool, **kwargs
        ):
            """
            输出的接口为一个二维列表，其中每行是一个列表，里面以两点的形式装着与同一个交叉点相交的全部直线，例如：
            [
              [[0,1,1,3,4,1],[21,12,1,123,231,1]], #代表一行，同一个交点的两条直线
              [[0,-1,1,3,-4,1],[21,-12,1,-123,231,1]] #另外两条直线
                                          ]
            """
            result = rectifier(
                self.cross_line_cache,
                src_path=self._bg_path,
                output_dir="./output_" + os.path.basename(self._bg_path).split(".")[0],
            )
            if is_show:
                cv2.imshow(winname="Result of rectification", mat=result)

        def enable_cross():
            """
            选出交叉点，决定参与计算的五组正交直线
            """
            self.mode = CROSS_MODE
            self.hline_cache = []
            self.cross_line_cache = []

        button1 = tk.Button(self._root, text="Line Mode", command=enable_lines)
        button2 = tk.Button(self._root, text="Point Mode", command=enable_point)
        button3 = tk.Button(self._root, text="Cross Choice", command=enable_cross)
        button4 = tk.Button(
            self._root,
            text="Rectification",
            command=partial(enable_finish, rectifier, is_show),
        )

        button1.place(x=100, y=10)
        button2.place(x=200, y=10)
        button3.place(x=300, y=10)
        button4.place(x=400, y=10)

    def _menu(self):
        def menu_load():
            bg_path = filedialog.askopenfilename()
            self._bg_image = tk.PhotoImage(file=bg_path)
            self._bg_path = bg_path
            # 创建一个 Canvas，用于绘制图形
            self._canvas = tk.Canvas(
                self._root, width=self._bg_image.width(), height=self._bg_image.height()
            )
            self._canvas.create_image(0, 0, anchor="nw", image=self._bg_image)
            self._canvas.place(x=0, y=60)
            # 绑定鼠标左键点击事件
            self._canvas.bind("<Button-1>", self.left_click)
            # 绑定鼠标右键点击事件，删除点
            self._canvas.bind("<Button-3>", self._delete_point)

        def menu_save():
            # 设置保存文件的默认文件名和文件类型
            file_type = [("Image Files", "*.png"), ("All Files", "*.*")]
            default_file_name = "ScreenShot_" + strftime("%Y-%m-%D-%H-%M-%S") + ".png"
            file_path = filedialog.asksaveasfilename(
                defaultextension=".png",
                filetypes=file_type,
                initialfile=default_file_name,
            )

            # 如果用户点击了取消按钮则返回空路径
            if file_path == "":
                print("用户取消文件保存操作")
            else:
                print("文件保存路径是：", file_path)

        def menu_remake():
            """
            重启程序
            """
            self._canvas.delete("all")
            self.points_manager = ManagerBase()
            self._hline = None  # type : Tags
            self._ind = 0
            self.mode = 0
            self.point_cache = []
            self.hline_cache = None
            self.cross_line_cache = []
            self._canvas.create_image(0, 0, anchor="nw", image=self._bg_image)

        menubar = tk.Menu(self._root)
        self._root.config(menu=menubar)
        filemenu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Files", menu=filemenu)
        filemenu.add_command(label="Load", command=menu_load)
        filemenu.add_command(label="Save", command=menu_save)
        filemenu.add_command(label="Remake", command=menu_remake)
        filemenu.add_separator()

    def run(self, rectifier: Callable[[Any], cv2.Mat], is_show: bool=False, **kwargs):
        self._menu()
        self._botton(rectifier=rectifier, is_show=is_show, **kwargs)
        self._root.geometry("1100x1000")
        self._root.mainloop()

    def _delete_point(self, event) -> None:
        """
        删除鼠标点击的点
        Arguments:
          - `event`: 鼠标点击事件
        """
        ind = self.points_manager.is_near(Point(event.x, event.y))
        if ind != -1:
            if self._hline != None:
                self.points_manager.pop(self._hline)
                self._hline = None
            self._canvas.delete("all")
            try:
                self.points_manager.pop(ind)
            except:
                pass
            self._canvas.create_image(0, 0, anchor="nw", image=self._bg_image)
            self._remake()
        else:
            print("你点击的位置没有点")

    def left_click(self, event, **kwargs):
        if self.mode == POINT_MODE:
            self.point_cache = []
            if self.hline_cache:
                (self._de_highlight_point(hline) for hline in self.hline_cache)
                self.hline_cache = []
            return self._click_position(event, **kwargs)
        elif self.mode == LINE_MODE:
            if self.hline_cache:
                (self._de_highlight_point(hline) for hline in self.hline_cache)
                self.hline_cache = []
            return self._draw_lines(event, **kwargs)
        elif self.mode == CROSS_MODE:
            return self._cross_decide(event)

    def _click_position(self, event, is_force: bool = False, **kwargs) -> Point:
        """
        输出鼠标点击的位置
        Arguments:
          - `event`: 鼠标点击事件
          - `is_force`: 是否强制关闭邻近点检测
        """
        p = Point(event.x, event.y)
        if is_force:
            print("你点击的位置是：", p)
            self._draw_point(p)
        else:
            ind = self.points_manager.is_near(p)
            if ind != -1:
                p = self.points_manager[ind][1]
                print("你点击的位置是：", p)
                self._hline = self._highlight_point(p)
            else:
                self._de_highlight_point()
                print("你点击的位置是：", p)
                self._draw_point(p)
        return p

    def _draw_point(self, point: Point) -> None:
        self.points_manager.append(0, point, id=self._ind)
        self._canvas.create_text(
            point.x,
            point.y,
            fill="red",
            tags=self._ind,
            text="X",
            width=2,
        )
        self._ind += 1

    def _highlight_point(self, point: Point, radius: int = 8) -> Tags:
        self._de_highlight_point()
        self.points_manager.append(1, point, radius, id=self._ind)
        self._canvas.create_oval(
            point.x - radius,
            point.y - radius,
            point.x + radius,
            point.y + radius,
            width=2,
            outline="green",
            tags=self._ind,
        )
        self._ind += 1
        return self._ind - 1

    def _de_highlight_point(self, hline: int = None) -> None:
        if hline == None and self._ind > 1 and self._hline != None:
            self._canvas.delete("all")
            self.points_manager.pop(self._hline)
            self._canvas.create_image(0, 0, anchor="nw", image=self._bg_image)
            self._remake()
            self._hline = None
        elif self._ind > 1 and self._hline != None:
            self._canvas.delete("all")
            self.points_manager.pop(hline)
            self._canvas.create_image(0, 0, anchor="nw", image=self._bg_image)
            self._remake()

    def _remake(self):
        for key, meta in iter(self.points_manager):
            if meta[0] == 0:
                self._canvas.create_text(
                    meta[1].x,
                    meta[1].y,
                    fill="red",
                    tags=key,
                    text="X",
                    width=2,
                )
            elif meta[0] == 1:
                self._canvas.create_oval(
                    meta[1].x - meta[2],
                    meta[1].y - meta[2],
                    meta[1].x + meta[2],
                    meta[1].y + meta[2],
                    width=2,
                    outline="green",
                    tags=key,
                )
            elif meta[0] == 2:
                self._canvas.create_line(
                    *meta[1].p1,
                    *meta[1].p2,
                    width=2,
                    fill="red",
                    tags=key,
                )

    def _draw_lines(self, event, is_force: bool = False) -> None:
        if len(self.point_cache) == 0:
            self.point_cache.append(self._click_position(event, is_force))
        elif len(self.point_cache) == 1:
            p = self._click_position(event, is_force)
            self.point_cache.append(p)
            line = Line(*self.point_cache)
            if not is_force and self.points_manager.is_near(line) != -1:
                self._canvas.create_line(
                    *line.p1,
                    *line.p2,
                    fill="red",
                    tags=self._ind,
                    width=2,
                )
                self._ind += 1
                self.point_cache.pop(0)
                ind = self.points_manager.is_near(self.point_cache.pop())
                self.point_cache.append(self.points_manager[ind][1])
            else:
                self.points_manager.append(2, line, id=self._ind)
                self._canvas.create_line(
                    *line.p1,
                    *line.p2,
                    fill="red",
                    tags=self._ind,
                    width=2,
                )
                self._ind += 1
                self.point_cache.pop(0)

    def _cross_decide(self, event):
        p = Point(event.x, event.y)
        ind = self.points_manager.is_near(p)
        if ind != -1:
            p_choice = self.points_manager[ind][1]
            print("你选中的交叉位置是：", p_choice)
            self.hline_cache.append(self._highlight_point(p_choice))
            temp = filterfalse(
                lambda x: not x[1].lay_on_line(p_choice),
                filterfalse(
                    lambda x: x[0] != 2,
                    self.points_manager.output(),
                ),
            )
            # 排除掉不是直线的成分，然后排除不与交叉点相交的直线，使用迭代器提高效率
            self.cross_line_cache.append(
                list(
                    map(
                        lambda x: [*x[1].p1, 1, *x[1].p2, 1],  # 齐次坐标的1
                        temp,
                    )
                )
            )


if __name__ == "__main__":
    ui = UI()
    ui.run(is_show=True)
